﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ApiScanner.Core
{
    public sealed class ExchangeDllScannerModule : IFileSystemScannerModule
    {
        private static readonly string[] _FilePatterns = new[] { "*.dll", "*.exe" };
        private readonly IList<string> _DllNames = new List<string> { "Mapi32.DLL", "MAPI32.dll", "CDOEX.DLL", "Cdoexm.DLL", "CDOEXM.DLL", "Cdoex.DLL", "CDO.DLL", "Cdo.DLL", "Exoledb.DLL", "EXOLEDB.DLL" };
        private static readonly IEnumerable<Encoding> _Encodings = new[] { Encoding.ASCII, Encoding.Unicode };

        public IEnumerable<string> FilePatterns
        {
            get
            {
                return _FilePatterns;
            }
        }

        public IList<string> DllNames
        {
            get { return _DllNames; }
        }

        public Artifact Execute(string file, FileSystemScannerModuleConfigurationElement configuration)
        {
            var extension = Path.GetExtension(file);
            if (!(extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)))
            {
                return null;
            }

            var filename = Path.GetFileName(file);
            if (_DllNames.Contains(filename, StringComparer.OrdinalIgnoreCase))
            {
                return null;
            }

            using (var stream = File.OpenRead(file))
            {
                var dllsFound = new List<string>();
                var buffer = new byte[85000];
                var searches = (from name in DllNames
                                let searchString = name
                                from encoding in _Encodings
                                let searchContent = encoding.GetBytes(searchString)
                                select new { Name = name, SearchContent = searchContent }).ToList();

                var offset = 0;
                do
                {
                    var padding = searches.Max(s => s.SearchContent.Length);
                    var bytesRead = stream.Read(buffer, offset, buffer.Length - offset);
                    if (bytesRead == 0) break;
                    var result = (from search in searches
                                  let searcher = new FastByteSearcher(buffer, search.SearchContent, 0, buffer.Length)
                                  let index = searcher.FindNext()
                                  where index != -1
                                  select search.Name).ToArray();
                    foreach (var name in result)
                    {
                        dllsFound.Add(name);
                        searches.RemoveAll(search => search.Name == name);
                    }

                    if (padding < bytesRead)
                    {
                        padding = bytesRead - padding;
                        Array.Copy(buffer, bytesRead - padding, buffer, 0, padding);
                        offset = padding;
                    }
                    else
                    {
                        offset = bytesRead;
                    }
                } while (searches.Count > 0);

                if (dllsFound.Count > 0)
                {
                    return new FileSystemArtifact(file, dllsFound.Distinct(StringComparer.OrdinalIgnoreCase));
                }
            }
            return null;
        }
    }
}